﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Gmantis.Controls.Extended
{
    [TemplatePart(Name = "Background", Type = typeof(Canvas))]
    public class CheckeredBorder : Control
    {
        // Fields
        internal Canvas _elementBackground;
        private bool _throwAlternatingSquareBrushChanged = true;
        private bool _throwSquareBrushChanged = true;
        private bool _throwSquareWidthChanged = true;
        public static readonly DependencyProperty AlternatingSquareBrushProperty = DependencyProperty.Register("AlternatingSquareBrush", typeof(Brush), typeof(CheckeredBorder), new PropertyMetadata(new SolidColorBrush(Colors.White), new PropertyChangedCallback(CheckeredBorder.OnAlternatingSquareBrushPropertyChanged)));
        internal const string BackgroundElementName = "Background";
        public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(CheckeredBorder), null);
        public static readonly DependencyProperty SquareBrushProperty = DependencyProperty.Register("SquareBrush", typeof(Brush), typeof(CheckeredBorder), new PropertyMetadata(new SolidColorBrush(Color.FromArgb(0xff, 0xd7, 0xd7, 0xd7)), new PropertyChangedCallback(CheckeredBorder.OnSquareBrushPropertyChanged)));
        public static readonly DependencyProperty SquareWidthProperty = DependencyProperty.Register("SquareWidth", typeof(double), typeof(CheckeredBorder), new PropertyMetadata(4.0, new PropertyChangedCallback(CheckeredBorder.OnSquareWidthPropertyChanged)));

        // Events
        public event EventHandler<PropertyChangedEventArgs<Brush>> AlternatingSquareBrushChanged;
        public event EventHandler<PropertyChangedEventArgs<Brush>> SquareBrushChanged;
        public event EventHandler<PropertyChangedEventArgs<double>> SquareWidthChanged;

        // Methods
        public CheckeredBorder()
        {
            base.DefaultStyleKey = typeof(CheckeredBorder);
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private void InitializeBackgroundPart()
        {
            this._elementBackground.SizeChanged += delegate(object s, SizeChangedEventArgs e)
            {
                RectangleGeometry geometry = new RectangleGeometry
                {
                    Rect = new Rect(0.0, 0.0, this._elementBackground.ActualWidth, this._elementBackground.ActualHeight),
                    RadiusX = this.CornerRadius.TopLeft,
                    RadiusY = this.CornerRadius.TopRight
                };
                this._elementBackground.Clip = geometry;
                this.UpdateSquares();
            };
        }

        private void OnAfterApplyTemplate()
        {
        }

        private void OnAlternatingSquareBrushChanged(Brush oldValue)
        {
            this.UpdateSquares();
        }

        private static void OnAlternatingSquareBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CheckeredBorder sender = d as CheckeredBorder;
            Brush oldValue = (Brush)e.OldValue;
            sender.OnAlternatingSquareBrushChanged(oldValue);
            if ((sender.AlternatingSquareBrushChanged != null) && sender._throwAlternatingSquareBrushChanged)
            {
                PropertyChangedEventArgs<Brush> args = new PropertyChangedEventArgs<Brush>
                {
                    OldValue = (Brush)e.OldValue,
                    NewValue = (Brush)e.NewValue
                };
                sender.AlternatingSquareBrushChanged(sender, args);
            }
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._elementBackground = this.GetTemplateChild<Canvas>("Background", true, ref errors);
            if (this._elementBackground != null)
            {
                this.InitializeBackgroundPart();
            }
            if (!string.IsNullOrEmpty(errors))
            {
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to CheckeredBorder.\nDetails: {0}", new object[] { errors }));
                }
            }
            else
            {
                this.OnAfterApplyTemplate();
            }
        }

        private void OnSquareBrushChanged(Brush oldValue)
        {
            this.UpdateSquares();
        }

        private static void OnSquareBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CheckeredBorder sender = d as CheckeredBorder;
            Brush oldValue = (Brush)e.OldValue;
            sender.OnSquareBrushChanged(oldValue);
            if ((sender.SquareBrushChanged != null) && sender._throwSquareBrushChanged)
            {
                PropertyChangedEventArgs<Brush> args = new PropertyChangedEventArgs<Brush>
                {
                    OldValue = (Brush)e.OldValue,
                    NewValue = (Brush)e.NewValue
                };
                sender.SquareBrushChanged(sender, args);
            }
        }

        private void OnSquareWidthChanged(double oldValue)
        {
            if (this.SquareWidth > 0.0)
            {
                this.UpdateSquares();
            }
            else
            {
                this.SquareWidth = oldValue;
            }
        }

        private static void OnSquareWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CheckeredBorder sender = d as CheckeredBorder;
            double oldValue = (double)e.OldValue;
            sender.OnSquareWidthChanged(oldValue);
            if ((sender.SquareWidthChanged != null) && sender._throwSquareWidthChanged)
            {
                PropertyChangedEventArgs<double> args = new PropertyChangedEventArgs<double>
                {
                    OldValue = (double)e.OldValue,
                    NewValue = (double)e.NewValue
                };
                sender.SquareWidthChanged(sender, args);
            }
        }

        private void UpdateSquares()
        {
            if (this._elementBackground != null)
            {
                this._elementBackground.Children.Clear();
                for (int i = 0; i < (this._elementBackground.ActualWidth / this.SquareWidth); i++)
                {
                    for (int j = 0; j < (this._elementBackground.ActualHeight / this.SquareWidth); j++)
                    {
                        Rectangle element = new Rectangle
                        {
                            Fill = ((((i % 2) == 0) && ((j % 2) == 0)) || (((i % 2) == 1) && ((j % 2) == 1))) ? this.SquareBrush : this.AlternatingSquareBrush,
                            Width = Math.Max(0.0, Math.Min(this.SquareWidth, this._elementBackground.ActualWidth - (i * this.SquareWidth))),
                            Height = Math.Max(0.0, Math.Min(this.SquareWidth, this._elementBackground.ActualHeight - (j * this.SquareWidth)))
                        };
                        Canvas.SetLeft(element, i * this.SquareWidth);
                        Canvas.SetTop(element, j * this.SquareWidth);
                        this._elementBackground.Children.Add(element);
                    }
                }
            }
        }

        // Properties
        public Brush AlternatingSquareBrush
        {
            get { return (Brush)GetValue(AlternatingSquareBrushProperty); }
            set { SetValue(AlternatingSquareBrushProperty, value); }
        }

        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        public Brush SquareBrush
        {
            get { return (Brush)GetValue(SquareBrushProperty); }
            set { SetValue(SquareBrushProperty, value); }
        }

        public double SquareWidth
        {
            get { return (double)GetValue(SquareWidthProperty); }
            set { SetValue(SquareWidthProperty, value); }
        }
    }
}
